/*  This file is part of "reprepro"
 *  Copyright (C) 2006,2007,2008 Bernhard R. Link
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02111-1301  USA
 */
#include <config.h>

#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <strings.h>
#include <malloc.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include "error.h"
#include "filecntl.h"
#include "mprintf.h"
#include "strlist.h"
#include "names.h"
#include "dirs.h"
#include "checksums.h"
#include "chunks.h"
#include "chunkedit.h"
#include "signature.h"
#include "debfile.h"
#include "sourceextraction.h"

/* for compatibility with used code */
int verbose=0;
bool interrupted(void) {return false;}

static void about(bool help) NORETURN;
static void about(bool help) {
	fprintf(help?stdout:stderr,
"modifychanges: Modify a Debian style .changes file\n"
"Syntax: modifychanges [--create] <changesfile> <commands>\n"
"Possible commands include:\n"
" verify\n"
" updatechecksums [<files to update>]\n"
" includeallsources [<files to copy from .dsc to .changes>]\n"
" adddeb <.deb filenames>\n"
" adddsc <.dsc filenames>\n"
" addrawfile <filenames>\n"
" add <filenames processed by filename suffix>\n"
" setdistribution <distributions to list>\n"
//" create <.dsc and .deb files to include>\n"
);
	if( help )
		exit(EXIT_SUCCESS);
	else
		exit(EXIT_FAILURE);
}

struct binaryfile {
	struct binaryfile *next; // in binaries.files list
	struct binary *binary; // parent
	struct fileentry *file;
	char *controlchunk;
	char *name, *version, *architecture;
	char *sourcename, *sourceversion;
	char *maintainer;
	char *section, *priority;
	char *shortdescription;
	bool hasmd5sums;
};

static void binaryfile_free(struct binaryfile *p) {
	if( p == NULL )
		return;

	free(p->controlchunk);
	free(p->name);
	free(p->version);
	free(p->architecture);
	free(p->sourcename);
	free(p->sourceversion);
	free(p->maintainer);
	free(p->section);
	free(p->priority);
	free(p->shortdescription);
	free(p);
}

enum filetype { ft_UNKNOWN,
			ft_TAR_GZ, ft_ORIG_TAR_GZ, ft_DIFF_GZ,
	                ft_TAR_BZ2, ft_ORIG_TAR_BZ2, ft_DIFF_BZ2,
	                ft_TAR_LZMA, ft_ORIG_TAR_LZMA, ft_DIFF_LZMA,
#define ft_MaxInSource ft_DSC-1
			ft_DSC, ft_DEB, ft_UDEB , ft_Count};
#define ft_Max ft_Count-1
static const char * const typesuffix[ft_Count] = { "?",
	".tar.gz", ".orig.tar.gz", ".diff.gz",
	".tar.bz2", ".orig.tar.bz2", ".diff.bz2",
	".tar.lzma", ".orig.tar.lzma", ".diff.lzma",
	".dsc", ".deb", ".udeb"};

struct dscfile {
	struct fileentry *file;
	char *name;
	char *version;
	struct strlist binaries;
	char *maintainer;
	char *controlchunk;
	struct strlist validkeys,keys;
	// hard to get:
	char *section, *priority;
	// TODO: check Architectures?
	struct checksumsarray expected;
	struct fileentry **uplink;
	bool parsed, modified;
};

static void dscfile_free(struct dscfile *p) {
	if( p == NULL )
		return;

	free(p->name);
	free(p->version);
	free(p->maintainer);
	free(p->controlchunk);
	strlist_done(&p->keys);
	strlist_done(&p->validkeys);
	free(p->section);
	free(p->priority);
	checksumsarray_done(&p->expected);
	free(p->uplink);
	free(p);
}

struct fileentry {
	struct fileentry *next;
	char *basename; size_t namelen;
	char *fullfilename;
	struct checksums *checksumsfromchanges, /* NULL means was not listed there yet */
			 *realchecksums;
	char *section, *priority;
	enum filetype type;
	/* only if type deb or udeb */
	struct binaryfile *deb;
	/* only if type dsc */
	struct dscfile *dsc;
	int refcount;
};
struct changes;
static struct fileentry *add_fileentry(struct changes *c, const char *basename, size_t len, bool source, /*@null@*//*@out@*/size_t *ofs_p);

struct changes {
	/* the filename of the .changes file */
	char *filename;
	/* directory of filename */
	char *basedir;
	/* Contents of the .changes file: */
	char *name;
	char *version;
	char *maintainer;
	char *control;
	struct strlist architectures;
	struct strlist distributions;
	size_t binarycount;
	struct binary {
		char *name;
		char *description;
		struct binaryfile *files;
		bool missedinheader, uncheckable;
	} *binaries;
	struct fileentry *files;
	bool modified;
};

static void fileentry_free(struct fileentry *f) {
	if( f == NULL )
		return;
	free(f->basename);
	free(f->fullfilename);
	checksums_free(f->checksumsfromchanges);
	checksums_free(f->realchecksums);
	free(f->section);
	free(f->priority);
	if( f->type == ft_DEB || f->type == ft_UDEB ) {
		binaryfile_free(f->deb);
	} else if( f->type == ft_DSC ) {
		dscfile_free(f->dsc);
	}
	free(f);
}

static void changes_free(struct changes *c) {
	unsigned int i;

	if( c == NULL )
		return;

	free(c->filename);
	free(c->basedir);
	free(c->name);
	free(c->version);
	free(c->maintainer);
	free(c->control);
	strlist_done(&c->architectures);
	strlist_done(&c->distributions);
	for( i = 0 ; i < c->binarycount ; i++ ) {
		free(c->binaries[i].name);
		free(c->binaries[i].description);
		// .files belongs elsewhere
	}
	free(c->binaries);
	while( c->files ) {
		struct fileentry *f = c->files;
		c->files = f->next;
		fileentry_free(f);
	}
	free(c);
}

static struct fileentry *add_fileentry(struct changes *c, const char *basename, size_t len, bool source, size_t *ofs_p) {
	struct fileentry **fp = &c->files;
	struct fileentry *f;
	size_t ofs = 0;

	while( (f=*fp) != NULL ) {
		if( f->namelen == len &&
				strncmp(basename,f->basename,len) == 0 )
			break;
		fp = &f->next;
		ofs++;
	}
	if( f == NULL ) {
		f = calloc(1,sizeof(struct fileentry));
		if( f == NULL )
			return NULL;
		*fp = f;
		f->basename = strndup(basename,len);
		f->namelen = len;
		/* guess type */
		for( f->type = source?ft_MaxInSource:ft_Max ;
				f->type > ft_UNKNOWN ; f->type-- ) {
			size_t l = strlen(typesuffix[f->type]);
			if( len > l && strcmp(f->basename+(len-l),
						typesuffix[f->type]) == 0 )
				break;
		}
	}
	if( ofs_p != NULL )
		*ofs_p = ofs;
	return f;
}

static retvalue searchforfile(const char *changesdir, const char *basename, /*@null@*/const struct strlist *searchpath, /*@null@*/const char *searchfirstin, char **result) {
	int i; bool found;
	char *fullname;

	if( searchfirstin != NULL ) {
		fullname = calc_dirconcat(searchfirstin, basename);
		if( FAILEDTOALLOC(fullname) )
			return RET_ERROR_OOM;
		if( isregularfile(fullname) ) {
			*result = fullname;
			return RET_OK;
		}
		free(fullname);
	}

	fullname = calc_dirconcat(changesdir, basename);
	if( FAILEDTOALLOC(fullname) )
		return RET_ERROR_OOM;

	found = isregularfile(fullname);
	i = 0;
	while( !found && searchpath != NULL && i < searchpath->count ) {
		free(fullname);
		fullname = calc_dirconcat(searchpath->values[i],
				basename);
		if( fullname == NULL )
			return RET_ERROR_OOM;
		if( isregularfile(fullname) ) {
			found = true;
			break;
		}
		i++;
	}
	if( found ) {
		*result = fullname;
		return RET_OK;
	} else {
		free(fullname);
		return RET_NOTHING;
	}
}

static retvalue findfile(const char *filename, const struct changes *c, /*@null@*/const struct strlist *searchpath, /*@null@*/const char *searchfirstin, char **result) {
	char *fullfilename;

	if( rindex(filename,'/') == NULL ) {
		retvalue r;

		r = searchforfile(c->basedir, filename,
				searchpath, searchfirstin, &fullfilename);
		if( !RET_IS_OK(r) )
			return r;
	} else {
		if( !isregularfile(filename) )
			return RET_NOTHING;
		fullfilename = strdup(filename);
		if( fullfilename == NULL ) {
			return RET_ERROR_OOM;
		}
	}
	*result = fullfilename;
	return RET_OK;
}

static retvalue add_file(struct changes *c, /*@only@*/char *basename, /*@only@*/char *fullfilename, enum filetype type, struct fileentry **file) {
	size_t basenamelen;
	struct fileentry **fp = &c->files;
	struct fileentry *f;

	basenamelen = strlen(basename);

	while( (f=*fp) != NULL ) {
		if( f->namelen == basenamelen &&
				strncmp(basename,f->basename,basenamelen) == 0 ) {
			*file = f;
			return RET_NOTHING;
		}
		fp = &f->next;
	}
	assert( f == NULL );
	f = calloc(1,sizeof(struct fileentry));
	if( f == NULL ) {
		return RET_ERROR_OOM;
	}
	f->basename = basename;
	f->namelen = basenamelen;
	f->fullfilename = fullfilename;
	f->type = type;

	*fp = f;
	*file = f;
	return RET_OK;
}


static struct binary *get_binary(struct changes *c, const char *p, size_t len) {
	unsigned int j;

	for( j = 0 ; j < c->binarycount ; j++ ) {
		if( strncmp(c->binaries[j].name, p, len) == 0 &&
				c->binaries[j].name[len] == '\0' )
			break;
	}
	if( j == c->binarycount ) {
		char *name = strndup(p, len);
		struct binary *n;

		if( name == NULL )
			return NULL;
		n = realloc(c->binaries,(j+1)*sizeof(struct binary));
		if( n == NULL ) {
			free(name);
			return NULL;
		}
		c->binaries = n;
		c->binarycount = j+1;
		c->binaries[j].name = name;
		c->binaries[j].description = NULL;
		c->binaries[j].files = NULL;
		c->binaries[j].missedinheader = true;
		c->binaries[j].uncheckable = false;
	}
	assert( j < c->binarycount );
	return &c->binaries[j];
}

static retvalue parse_changes_description(struct changes *c, struct strlist *tmp) {
	int i;

	for( i = 0 ; i < tmp->count ; i++ ) {
		struct binary *b;
		const char *p = tmp->values[i];
		const char *e = p;
		const char *d;
		while( *e != '\0' && *e != ' ' && *e != '\t' )
			e++;
		d = e;
		while( *d == ' ' || *d == '\t' )
			d++;
		if( *d == '-' )
			d++;
		while( *d == ' ' || *d == '\t' )
			d++;

		b = get_binary(c, p, e-p);
		if( b == NULL )
			return RET_ERROR_OOM;

		b->description = strdup(d);
		if( b->description == NULL )
			return RET_ERROR_OOM;
	}
	return RET_OK;
}

static retvalue parse_changes_files(struct changes *c, struct strlist filelines[cs_hashCOUNT]) {
	int i;
	struct fileentry *f;
	retvalue r;
	struct hashes *hashes;
	struct strlist *tmp;
	size_t ofs, count = 0;
	enum checksumtype cs;

	tmp = &filelines[cs_md5sum];
	hashes = calloc(tmp->count, sizeof(struct hashes));
	if( FAILEDTOALLOC(hashes) )
		return RET_ERROR_OOM;

	for( i = 0 ; i < tmp->count ; i++ ) {
		char *p;
		const char *md5start, *md5end, *sizestart, *sizeend, *sectionstart, *sectionend, *priostart, *prioend, *filestart, *fileend;
		p = tmp->values[i];
#undef xisspace
#define xisspace(c) (c == ' ' || c == '\t')
		while( *p !='\0' && xisspace(*p) )
			p++;
		md5start = p;
		while( (*p >= '0' && *p <= '9') ||
				(*p >= 'A' && *p <= 'F' ) ||
				(*p >= 'a' && *p <= 'f' ) ) {
			if( *p >= 'A' && *p <= 'F' )
				(*p) += 'a' - 'A';
			p++;
		}
		md5end = p;
		while( *p !='\0' && !xisspace(*p) )
			p++;
		while( *p !='\0' && xisspace(*p) )
			p++;
		while( *p == '0' && ( '0' <= p[1] && p[1] <= '9' ) )
			p++;
		sizestart = p;
		while( (*p >= '0' && *p <= '9') )
			p++;
		sizeend = p;
		while( *p !='\0' && !xisspace(*p) )
			p++;
		while( *p !='\0' && xisspace(*p) )
			p++;
		sectionstart = p;
		while( *p !='\0' && !xisspace(*p) )
			p++;
		sectionend = p;
		while( *p !='\0' && xisspace(*p) )
			p++;
		priostart = p;
		while( *p !='\0' && !xisspace(*p) )
			p++;
		prioend = p;
		while( *p !='\0' && xisspace(*p) )
			p++;
		filestart = p;
		while( *p !='\0' && !xisspace(*p) )
			p++;
		fileend = p;
		while( *p !='\0' && xisspace(*p) )
			p++;
		if( *p != '\0' ) {
			fprintf(stderr,"Unexpected sixth argument in '%s'!\n", tmp->values[i]);
			return RET_ERROR;
		}
		if( fileend - filestart == 0 )
			continue;
		f = add_fileentry(c, filestart, fileend-filestart, false, &ofs);
		assert( ofs <= count );
		if( ofs == count )
			count++;
		if( hashes[ofs].hashes[cs_md5sum].start != NULL ) {
			fprintf(stderr, "WARNING: Multiple occourance of '%s' in .changes file!\nIgnoring all but the first one.\n",
					f->basename);
			continue;
		}
		hashes[ofs].hashes[cs_md5sum].start = md5start;
		hashes[ofs].hashes[cs_md5sum].len = md5end - md5start;
		hashes[ofs].hashes[cs_length].start = sizestart;
		hashes[ofs].hashes[cs_length].len = sizeend - sizestart;

		if( sectionend - sectionstart == 1 && *sectionstart == '-' ) {
			f->section = NULL;
		} else {
			f->section = strndup(sectionstart,sectionend-sectionstart);
			if( f->section == NULL )
				return RET_ERROR_OOM;
		}
		if( prioend - priostart == 1 && *priostart == '-' ) {
			f->priority = NULL;
		} else {
			f->priority = strndup(priostart,prioend-priostart);
			if( f->priority == NULL )
				return RET_ERROR_OOM;
		}
	}
	const char * const hashname[cs_hashCOUNT] = {"Md5", "Sha1" /*, "Sha256"*/ };
	for( cs = cs_firstEXTENDED ; cs < cs_hashCOUNT ; cs++ ) {
		tmp = &filelines[cs];

		for( i = 0 ; i < tmp->count ; i++ ) {
			char *p;
			const char *hashstart, *hashend, *sizestart, *sizeend, *filestart, *fileend;;
			p = tmp->values[i];
			while( *p !='\0' && xisspace(*p) )
				p++;
			hashstart = p;
			while( (*p >= '0' && *p <= '9') ||
					(*p >= 'A' && *p <= 'F' ) ||
					(*p >= 'a' && *p <= 'f' ) ) {
				if( *p >= 'A' && *p <= 'F' )
					(*p) += 'a' - 'A';
				p++;
			}
			hashend = p;
			while( *p !='\0' && !xisspace(*p) )
				p++;
			while( *p !='\0' && xisspace(*p) )
				p++;
			while( *p == '0' && ( '0' <= p[1] && p[1] <= '9' ) )
				p++;
			sizestart = p;
			while( (*p >= '0' && *p <= '9') )
				p++;
			sizeend = p;
			while( *p !='\0' && !xisspace(*p) )
				p++;
			while( *p !='\0' && xisspace(*p) )
				p++;
			filestart = p;
			while( *p !='\0' && !xisspace(*p) )
				p++;
			fileend = p;
			while( *p !='\0' && xisspace(*p) )
				p++;
			if( *p != '\0' ) {
				fprintf(stderr,"Unexpected forth argument in '%s'!\n", tmp->values[i]);
				return RET_ERROR;
			}
			if( fileend - filestart == 0 )
				continue;
			f = add_fileentry(c, filestart, fileend-filestart, false, &ofs);
			assert( ofs <= count );
			// until md5sums are no longer obligatory:
			if( ofs == count )
				continue;
			if( hashes[ofs].hashes[cs].start != NULL ) {
				fprintf(stderr, "WARNING: Multiple occourance of '%s' in Checksums-'%s' of .changes file!\nIgnoring all but the first one.\n",
						f->basename, hashname[cs]);
				continue;
			}
			hashes[ofs].hashes[cs].start = hashstart;
			hashes[ofs].hashes[cs].len = hashend - hashstart;
			// TODO: compare instead:
			// hashes[ofs].hashes[cs_length].start = sizestart;
			// hashes[ofs].hashes[cs_length].len = sizeend - sizestart;
		}
	}
	ofs = 0;
	for( f = c->files ; f != NULL ; f = f->next, ofs++ ) {
		r = checksums_initialize(&f->checksumsfromchanges,
				hashes[ofs].hashes);
		if( RET_WAS_ERROR(r) )
			return r;
	}
	assert( count == ofs );

	return RET_OK;
}

static retvalue read_dscfile(const char *fullfilename, struct dscfile **dsc) {
	struct dscfile *n;
	struct strlist filelines[cs_hashCOUNT];
	enum checksumtype cs;
	retvalue r;

	n = calloc(1,sizeof(struct dscfile));
	if( n == NULL )
		return RET_ERROR_OOM;
	r = signature_readsignedchunk(fullfilename, fullfilename,
			&n->controlchunk, &n->validkeys, &n->keys, NULL);
	assert( r != RET_NOTHING );
	// TODO: can this be ignored sometimes?
	if( RET_WAS_ERROR(r) ) {
		free(n);
		return r;
	}
	r = chunk_getname(n->controlchunk, "Source", &n->name, false);
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Maintainer",&n->maintainer);
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Version",&n->version);
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(n);
		return r;
	}

	/* unusally not here, but hidden in the contents */
	r = chunk_getvalue(n->controlchunk, "Section",&n->section);
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(n);
		return r;
	}
	/* dito */
	r = chunk_getvalue(n->controlchunk, "Priority",&n->priority);
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(n);
		return r;
	}

	for( cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++ ) {
		assert( source_checksum_names[cs] != NULL );
		r = chunk_getextralinelist(n->controlchunk,
				source_checksum_names[cs], &filelines[cs]);
		if( r == RET_NOTHING ) {
			if( cs == cs_md5sum ) {
				fprintf(stderr,
"Error: Missing 'Files' entry in '%s'!\n",             fullfilename);
				r = RET_ERROR;
			}
			strlist_init(&filelines[cs]);
		}
		if( RET_WAS_ERROR(r) ) {
			while( cs-- > cs_md5sum ) {
				strlist_done(&filelines[cs]);
			}
			dscfile_free(n);
			return r;
		}
	}
	r = checksumsarray_parse(&n->expected, filelines, fullfilename);
	for( cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++ ) {
		strlist_done(&filelines[cs]);
	}
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(n);
		return r;
	}
	if( n->expected.names.count > 0 ) {
		n->uplink = calloc(n->expected.names.count,
				sizeof(struct fileentry*));
		if( FAILEDTOALLOC(n->uplink) ) {
			dscfile_free(n);
			return RET_ERROR_OOM;
		}
	}
	*dsc = n;
	return RET_OK;
}

static retvalue parse_dsc(struct fileentry *dscfile, struct changes *changes) {
	struct dscfile *n;
	retvalue r;
	size_t i;

	if( dscfile->fullfilename == NULL )
		return RET_NOTHING;
	r = read_dscfile(dscfile->fullfilename, &n);
	assert( r != RET_NOTHING );
	if( RET_WAS_ERROR(r) )
		return r;
	for( i =  0 ; i < n->expected.names.count ; i++ ) {
		const char *basename = n->expected.names.values[i];
		n->uplink[i] = add_fileentry(changes,
				basename, strlen(basename),
				true, NULL);
		if( n->uplink[i] == NULL ) {
			dscfile_free(n);
			return RET_ERROR_OOM;
		}
	}
	dscfile->dsc = n;
	return RET_OK;
}

#define DSC_WRITE_FILES 1
#define DSC_WRITE_ALL 0xFFFF
#define flagset(a) (flags & a) != 0

static retvalue write_dsc_file(struct fileentry *dscfile, unsigned int flags) {
	struct dscfile *dsc = dscfile->dsc;
	unsigned int i;
	struct chunkeditfield *cef;
	retvalue r;
	char *control; size_t controllen;
	struct checksums *checksums;
	char *destfilename;
	enum checksumtype cs;

	if( flagset(DSC_WRITE_FILES) ) {
		cef = NULL;
		for( cs = cs_hashCOUNT ; (cs--) > cs_md5sum ;  ) {
			cef = cef_newfield(source_checksum_names[cs],
					CEF_ADD, CEF_LATE,
					dsc->expected.names.count, cef);
			if( cef == NULL )
				return RET_ERROR_OOM;
			for( i = 0 ; i < dsc->expected.names.count ; i++ ) {
				const char *basename = dsc->expected.names.values[i];
				const char *hash, *size;
				size_t hashlen, sizelen;

				if( !checksums_gethashpart(dsc->expected.checksums[i],
							cs, &hash, &hashlen,
							&size, &sizelen) ) {
					assert( cs != cs_md5sum );
					cef = cef_pop(cef);
					break;
				}
				cef_setline2(cef, i, hash, hashlen, size, sizelen,
						1, basename, NULL);
			}
		}
	} else
		cef = NULL;

	r = chunk_edit(dsc->controlchunk, &control, &controllen, cef);
	cef_free(cef);
	if( RET_WAS_ERROR(r) )
		return r;
	assert( RET_IS_OK(r) );

	// TODO: try to add the signatures to it again...

	// TODO: add options to place changed files in different directory...
	if( dscfile->fullfilename != NULL ) {
		destfilename = strdup(dscfile->fullfilename);
	} else
		destfilename = strdup(dscfile->basename);
	if( destfilename == NULL ) {
		free(control);
		return RET_ERROR_OOM;
	}

	r = checksums_replace(destfilename, control, controllen, &checksums);
	if( RET_WAS_ERROR(r) ) {
		free(destfilename);
		free(control);
		return r;
	}
	assert( RET_IS_OK(r) );

	free(dscfile->fullfilename);
	dscfile->fullfilename = destfilename;
	checksums_free(dscfile->realchecksums);
	dscfile->realchecksums = checksums;
	free(dsc->controlchunk);
	dsc->controlchunk = control;
	return RET_OK;
}

static retvalue read_binaryfile(const char *fullfilename, struct binaryfile **result) {
	retvalue r;
	struct binaryfile *n;

	n = calloc(1,sizeof(struct binaryfile));
	if( n == NULL )
		return RET_ERROR_OOM;

	r = extractcontrol(&n->controlchunk, fullfilename);
	if( !RET_IS_OK(r)) {
		free(n);
		if( r == RET_ERROR_OOM )
			return r;
		else
			return RET_NOTHING;
	}

	r = chunk_getname(n->controlchunk, "Package", &n->name, false);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Version", &n->version);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	r = chunk_getnameandversion(n->controlchunk, "Source",
			&n->sourcename, &n->sourceversion);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Maintainer", &n->maintainer);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Architecture", &n->architecture);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Section",&n->section);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Priority",&n->priority);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	r = chunk_getvalue(n->controlchunk, "Description",&n->shortdescription);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(n);
		return r;
	}
	*result = n;
	return RET_OK;
}

static retvalue parse_deb(struct fileentry *debfile, struct changes *changes) {
	retvalue r;
	struct binaryfile *n;

	if( debfile->fullfilename == NULL )
		return RET_NOTHING;
	r = read_binaryfile(debfile->fullfilename, &n);
	if( !RET_IS_OK(r) )
		return r;
	if( n->name != NULL ) {
		n->binary = get_binary(changes, n->name, strlen(n->name));
		if( n->binary == NULL ) {
			binaryfile_free(n);
			return RET_ERROR_OOM;
		}
		n->next = n->binary->files;
		n->binary->files = n;
	}

	debfile->deb = n;
	return RET_OK;
}

static retvalue processfiles(const char *changesfilename, struct changes *changes,
		const struct strlist *searchpath) {
	char *dir;
	struct fileentry *file;
	retvalue r;

	r = dirs_getdirectory(changesfilename, &dir);
	if( RET_WAS_ERROR(r) )
		return r;

	for( file = changes->files; file != NULL ; file = file->next ) {
		assert( file->fullfilename == NULL );

		r = searchforfile(dir, file->basename, searchpath, NULL,
				&file->fullfilename);

		if( RET_IS_OK(r) ) {
			if( file->type == ft_DSC )
				r = parse_dsc(file,changes);
			else if( file->type == ft_DEB || file->type == ft_UDEB )
				r = parse_deb(file,changes);
			if( RET_WAS_ERROR(r) ) {
				free(dir);
				return r;
			}
		}

		if( r == RET_NOTHING ) {
			/* apply heuristics when not readable */
			if( file->type == ft_DSC ) {
			} else if( file->type == ft_DEB || file->type == ft_UDEB ) {
				struct binary *b; size_t len;

				len = 0;
				while( file->basename[len] != '_' &&
						file->basename[len] != '\0' )
					len++;
				b = get_binary(changes, file->basename, len);
				if( b == NULL ) {
					free(dir);
					return RET_ERROR_OOM;
				}
				b->uncheckable = true;
			}
		}
	}
	free(dir);
	return RET_OK;
}

static retvalue parse_changes(const char *changesfile, const char *chunk, struct changes **changes, const struct strlist *searchpath) {
	retvalue r;
	struct strlist tmp;
	struct strlist filelines[cs_hashCOUNT];
	enum checksumtype cs;
#define R if( RET_WAS_ERROR(r) ) { changes_free(n); return r; }

	struct changes *n = calloc(1,sizeof(struct changes));
	if( n == NULL )
		return RET_ERROR_OOM;
	n->filename = strdup(changesfile);
	if( n->filename == NULL ) {
		changes_free(n);
		return RET_ERROR_OOM;
	}
	r = dirs_getdirectory(changesfile, &n->basedir);
	R;
	// TODO: do getname here? trim spaces?
	r = chunk_getvalue(chunk, "Source", &n->name);
	R;
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Missing 'Source:' field in %s!\n", changesfile);
		n->name = NULL;
	}
	r = chunk_getvalue(chunk, "Version", &n->version);
	R;
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Missing 'Version:' field in %s!\n", changesfile);
		n->version = NULL;
	}
	r = chunk_getwordlist(chunk, "Architecture", &n->architectures);
	R;
	if( r == RET_NOTHING )
		strlist_init(&n->architectures);
	r = chunk_getwordlist(chunk, "Distribution", &n->distributions);
	R;
	if( r == RET_NOTHING )
		strlist_init(&n->distributions);
	r = chunk_getvalue(chunk, "Maintainer", &n->maintainer);
	R;
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Missing 'Maintainer:' field in %s!\n", changesfile);
		n->maintainer = NULL;
	}
	r = chunk_getuniqwordlist(chunk, "Binary", &tmp);
	R;
	if( r == RET_NOTHING ) {
		n->binaries = NULL;
	} else {
		int i;

		assert( RET_IS_OK(r) );
		n->binaries = calloc(tmp.count, sizeof(struct binary));
		if( n->binaries == NULL ) {
			changes_free(n);
			return RET_ERROR_OOM;
		}
		for( i = 0 ; i < tmp.count ; i++ ) {
			n->binaries[i].name = tmp.values[i];
		}
		n->binarycount = tmp.count;
		free(tmp.values);
	}
	r = chunk_getextralinelist(chunk, "Description", &tmp);
	R;
	if( RET_IS_OK(r) ) {
		r = parse_changes_description(n, &tmp);
		strlist_done(&tmp);
		if( RET_WAS_ERROR(r) ) {
			changes_free(n);
			return RET_ERROR_OOM;
		}
	}
	for( cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++ ) {
		assert( changes_checksum_names[cs] != NULL );
		r = chunk_getextralinelist(chunk,
				changes_checksum_names[cs], &filelines[cs]);
		if( r == RET_NOTHING ) {
			if( cs == cs_md5sum )
				break;
			strlist_init(&filelines[cs]);
		}
		if( RET_WAS_ERROR(r) ) {
			while( cs-- > cs_md5sum ) {
				strlist_done(&filelines[cs]);
			}
			changes_free(n);
			return r;
		}
	}
	if( cs == cs_hashCOUNT ) {
		r = parse_changes_files(n, filelines);
		for( cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++ ) {
			strlist_done(&filelines[cs]);
		}
		if( RET_WAS_ERROR(r) ) {
			changes_free(n);
			return RET_ERROR_OOM;
		}
	}
	r = processfiles(changesfile, n, searchpath);
	R;
	*changes = n;
	return RET_OK;
}

#define CHANGES_WRITE_FILES		0x01
#define CHANGES_WRITE_BINARIES		0x02
#define CHANGES_WRITE_SOURCE		0x04
#define CHANGES_WRITE_VERSION		0x08
#define CHANGES_WRITE_ARCHITECTURES	0x10
#define CHANGES_WRITE_MAINTAINER 	0x20
#define CHANGES_WRITE_DISTRIBUTIONS 	0x40
#define CHANGES_WRITE_ALL 	      0xFFFF

static retvalue write_changes_file(const char *changesfilename,struct changes *c, unsigned int flags) {
	struct chunkeditfield *cef;
	char datebuffer[100];
	retvalue r;
	char *control; size_t controllen;
	unsigned int filecount = 0;
	struct fileentry *f;
	struct tm *tm; time_t t;
	unsigned int i;
	struct strlist binaries;
	enum checksumtype cs;

	strlist_init(&binaries);

	for( f = c->files; f != NULL ; f = f->next ) {
		if( f->checksumsfromchanges != NULL )
			filecount++;
	}

	if( flagset(CHANGES_WRITE_FILES) ) {
		cef = NULL;
		for( cs = cs_hashCOUNT ; (cs--) > cs_md5sum ;  ) {
			cef = cef_newfield(changes_checksum_names[cs],
					CEF_ADD, CEF_LATE, filecount, cef);
			if( cef == NULL )
				return RET_ERROR_OOM;
			i = 0;
			for( f = c->files; f != NULL ; f = f->next ) {
				const char *hash, *size;
				size_t hashlen, sizelen;

				if( f->checksumsfromchanges == NULL )
					continue;
				if( !checksums_gethashpart(f->checksumsfromchanges,
							cs, &hash, &hashlen,
							&size, &sizelen) ) {
					assert( cs != cs_md5sum );
					cef = cef_pop(cef);
					break;
				}
				if( cs == cs_md5sum )
					cef_setline2(cef, i,
						hash, hashlen, size, sizelen,
						3,
						f->section?f->section:"-",
						f->priority?f->priority:"-",
						f->basename, NULL);
				else
					/* strange way, but as dpkg-genchanges
					 * does it this way... */
					cef_setline2(cef, i,
						hash, hashlen, size, sizelen,
						1,
						f->basename, NULL);
				i++;
			}
			assert( f != NULL || i == filecount );
		}
	} else {
		cef = cef_newfield("Files", CEF_KEEP, CEF_LATE, 0, NULL);
		if( cef == NULL )
			return RET_ERROR_OOM;
	}
	cef = cef_newfield("Changes", CEF_KEEP, CEF_LATE, 0, cef);
	if( cef == NULL )
			return RET_ERROR_OOM;
	cef = cef_newfield("Closes", CEF_KEEP, CEF_LATE, 0, cef);
	if( cef == NULL )
			return RET_ERROR_OOM;
	if( flagset(CHANGES_WRITE_BINARIES) ) {
		unsigned int count = 0;
		for( i = 0 ; i < c->binarycount ; i++ ) {
			const struct binary *b = c->binaries + i;
			if( b->description != NULL )
				count++;
		}
		cef = cef_newfield("Description", CEF_ADD, CEF_LATE,
				count, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
		count = 0;
		for( i = 0 ; i < c->binarycount ; i++ ) {
			const struct binary *b = c->binaries + i;
			if( b->description == NULL )
				continue;
			cef_setline(cef, count++, 3,
					b->name,
					"-",
					b->description,
					NULL);
		}

	}
	// Changed-by: line
	if( flagset(CHANGES_WRITE_MAINTAINER) ) {
		cef = cef_newfield("Maintainer", CEF_ADD, CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
		cef_setdata(cef, c->maintainer);
	} else {
		cef = cef_newfield("Maintainer", CEF_KEEP, CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
	}
	cef = cef_newfield("Urgency", CEF_KEEP, CEF_EARLY, 0, cef);
	if( cef == NULL )
			return RET_ERROR_OOM;
	cef = cef_newfield("Distribution", CEF_KEEP, CEF_EARLY, 0, cef);
	if( cef == NULL )
			return RET_ERROR_OOM;
	if( c->distributions.count > 0 ) {
		if( flagset(CHANGES_WRITE_DISTRIBUTIONS) )
			cef = cef_newfield("Distribution", CEF_ADD,
					CEF_EARLY, 0, cef);
		else
			cef = cef_newfield("Distribution", CEF_ADDMISSED,
					CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
		cef_setwordlist(cef, &c->distributions);
	} else if( flagset(CHANGES_WRITE_DISTRIBUTIONS) ) {
		cef = cef_newfield("Distribution", CEF_DELETE,
				CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
	}
	if( c->version != NULL ) {
		if( flagset(CHANGES_WRITE_VERSION) )
			cef = cef_newfield("Version", CEF_ADD,
					CEF_EARLY, 0, cef);
		else
			cef = cef_newfield("Version", CEF_ADDMISSED,
					CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
		cef_setdata(cef, c->version);
	} else if( flagset(CHANGES_WRITE_VERSION) ) {
		cef = cef_newfield("Version", CEF_DELETE,
				CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
	}
	if( flagset(CHANGES_WRITE_ARCHITECTURES) ) {
		cef = cef_newfield("Architecture", CEF_ADD, CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
		cef_setwordlist(cef, &c->architectures);
	} else {
		cef = cef_newfield("Architecture", CEF_KEEP, CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
	}
	if( flagset(CHANGES_WRITE_BINARIES) ) {
		r = strlist_init_n(c->binarycount, &binaries);
		if( RET_WAS_ERROR(r) ) {
			cef_free(cef);
			return r;
		}
		assert( RET_IS_OK(r) );
		for( i = 0 ; i < c->binarycount ; i++ ) {
			const struct binary *b = c->binaries + i;
			if( !b->missedinheader ) {
				r = strlist_add_dup(&binaries, b->name);
				if( RET_WAS_ERROR(r) ) {
					strlist_done(&binaries);
					cef_free(cef);
					return r;
				}
			}
		}
		cef = cef_newfield("Binary", CEF_ADD, CEF_EARLY, 0, cef);
		if( cef == NULL ) {
			strlist_done(&binaries);
			return RET_ERROR_OOM;
		}
		cef_setwordlist(cef, &binaries);
	} else {
		cef = cef_newfield("Binary", CEF_KEEP, CEF_EARLY, 0, cef);
		if( cef == NULL )
			return RET_ERROR_OOM;
	}
	if( c->name != NULL ) {
		if( flagset(CHANGES_WRITE_SOURCE) )
			cef = cef_newfield("Source", CEF_ADD,
					CEF_EARLY, 0, cef);
		else
			cef = cef_newfield("Source", CEF_ADDMISSED,
					CEF_EARLY, 0, cef);
		if( cef == NULL ) {
			strlist_done(&binaries);
			return RET_ERROR_OOM;
		}
		cef_setdata(cef, c->name);
	} else if( flagset(CHANGES_WRITE_SOURCE) ) {
		cef = cef_newfield("Source", CEF_DELETE,
				CEF_EARLY, 0, cef);
		if( cef == NULL ) {
			strlist_done(&binaries);
			return RET_ERROR_OOM;
		}
	}
	// TODO: if localized make sure this uses C locale....
	t = time(NULL);
        if( (tm = localtime(&t)) != NULL &&
	    strftime(datebuffer, sizeof(datebuffer)-1,
		    "%a, %e %b %Y %H:%M:%S %Z", tm) > 0 ) {
		cef = cef_newfield("Date", CEF_ADD, CEF_EARLY, 0, cef);
		if( cef == NULL ) {
			strlist_done(&binaries);
			return RET_ERROR_OOM;
		}
		cef_setdata(cef, datebuffer);
	} else {
		cef = cef_newfield("Date", CEF_DELETE,
				CEF_EARLY, 0, cef);
		if( cef == NULL ) {
			strlist_done(&binaries);
			return RET_ERROR_OOM;
		}
	}
	cef = cef_newfield("Format", CEF_ADDMISSED, CEF_EARLY, 0, cef);
	if( cef == NULL ) {
		strlist_done(&binaries);
		return RET_ERROR_OOM;
	}
	cef_setdata(cef, "1.7");

	r = chunk_edit((c->control==NULL)?"":c->control, &control, &controllen, cef);
	strlist_done(&binaries);
	cef_free(cef);
	if( RET_WAS_ERROR(r) )
		return r;
	assert( RET_IS_OK(r) );

	// TODO: try to add the signatures to it again...

	// TODO: add options to place changed files in different directory...

	r = checksums_replace(changesfilename, control, controllen, NULL);
	if( RET_WAS_ERROR(r) ) {
		free(control);
		return r;
	}
	assert( RET_IS_OK(r) );

	free(c->control);
	c->control = control;
	return RET_OK;
}

static retvalue getchecksums(struct changes *changes) {
	struct fileentry *file;
	retvalue r;

	for( file = changes->files; file != NULL ; file = file->next ) {

		if( file->fullfilename == NULL )
			continue;
		assert( file->realchecksums == NULL );

		r = checksums_read(file->fullfilename, &file->realchecksums);
		if( r == RET_ERROR_OOM )
			return r;
		else if( !RET_IS_OK(r) ) {
			// assume everything else is not fatal and means
			// a file not readable...
			file->realchecksums = NULL;
		}
	}
	return RET_OK;
}

static void verify_sourcefile_checksums(struct dscfile *dsc, int i, const char *dscfile) {
	const struct fileentry * const file = dsc->uplink[i];
	const struct checksums * const expectedchecksums = dsc->expected.checksums[i];
	const char * const basename = dsc->expected.names.values[i];
	assert( file != NULL );

	if( file->checksumsfromchanges == NULL ) {
		if( endswith(basename, typesuffix[ft_ORIG_TAR_GZ])
		|| endswith(basename, typesuffix[ft_ORIG_TAR_BZ2])) {
			fprintf(stderr,
"Not checking checksums of '%s', as not included in .changes file.\n",
				basename);
			return;
		} else if( file->realchecksums == NULL ) {
			fprintf(stderr,
"ERROR: File '%s' mentioned in '%s' was not found and is not mentioned in the .changes!\n",
				basename, dscfile);
			return;
		}
	}
	if( file->realchecksums == NULL )
		/* there will be an message later about that */
		return;
	if( checksums_check(expectedchecksums, file->realchecksums, NULL))
		return;

	if( file->checksumsfromchanges != NULL &&
	    checksums_check(expectedchecksums, file->checksumsfromchanges, NULL) )
		fprintf(stderr,
"ERROR: checksums of '%s' differ from the ones listed in both '%s' and the .changes file!\n",
				basename, dscfile);
	else {
		fprintf(stderr,
"ERROR: checksums of '%s' differ from those listed in '%s':\n!\n",
				basename, dscfile);
		checksums_printdifferences(stderr,
				expectedchecksums, file->realchecksums);
	}
}

static void verify_binary_name(const char *basename, const char *name, const char *version, const char *architecture, enum filetype type) {
	size_t nlen, vlen, alen;
	const char *versionwithoutepoch;

	if( name == NULL )
		return;
	nlen = strlen(name);
	if( strncmp(basename, name, nlen) != 0 || basename[nlen] != '_' ) {
		fprintf(stderr,
"ERROR: '%s' does not start with '%s_' as expected!\n",
					basename, name);
		return;
	}
	if( version == NULL )
		return;
	versionwithoutepoch = strchr(version, ':');
	if( versionwithoutepoch == NULL )
		versionwithoutepoch = version;
	else
		versionwithoutepoch++;
	vlen = strlen(versionwithoutepoch);
	if( strncmp(basename+nlen+1, versionwithoutepoch, vlen) != 0
	|| basename[nlen+1+vlen] != '_' ) {
		fprintf(stderr,
"ERROR: '%s' does not start with '%s_%s_' as expected!\n",
			basename, name, version);
		return;
	}
	if( architecture == NULL )
		return;
	alen = strlen(architecture);
	if( strncmp(basename+nlen+1+vlen+1, architecture, alen) != 0
	|| strcmp(basename+nlen+1+vlen+1+alen, typesuffix[type]) != 0 )
		fprintf(stderr,
"ERROR: '%s' is not called '%s_%s_%s%s' as expected!\n",
			basename, name, versionwithoutepoch,
			architecture, typesuffix[type]);
}

static retvalue verify(const char *changesfilename, struct changes *changes) {
	retvalue r;
	struct fileentry *file;
	unsigned int j;

	printf("Checking Source packages...\n");
	for( file = changes->files; file != NULL ; file = file->next ) {
		const char *name, *version, *p;
		size_t namelen, versionlen, l;
		bool has_tar, has_diff, has_orig;

		if( file->type != ft_DSC )
			continue;
		if( !strlist_in(&changes->architectures, "source") ) {
			fprintf(stderr,
"ERROR: '%s' contains a .dsc, but does not list Architecture 'source'!\n",
				changesfilename);
		}
		if( file->fullfilename == NULL ) {
			fprintf(stderr,
"ERROR: Could not find '%s'!\n", file->basename);
			continue;
		}
		if( file->dsc == NULL ) {
			fprintf(stderr,
"WARNING: Could not read '%s', thus it cannot be checked!\n", file->fullfilename);
			continue;
		}
		if( file->dsc->name == NULL )
			fprintf(stderr,
"ERROR: '%s' does not contain a 'Source:' header!\n", file->fullfilename);
		else if( changes->name != NULL &&
				strcmp(changes->name, file->dsc->name) != 0 )
			fprintf(stderr,
"ERROR: '%s' lists Source '%s' while .changes lists '%s'!\n",
				file->fullfilename,
				file->dsc->name, changes->name);
		if( file->dsc->version == NULL )
			fprintf(stderr,
"ERROR: '%s' does not contain a 'Version:' header!\n", file->fullfilename);
		else if( changes->version != NULL &&
				strcmp(changes->version, file->dsc->version) != 0 )
			fprintf(stderr,
"ERROR: '%s' lists Version '%s' while .changes lists '%s'!\n",
				file->fullfilename,
				file->dsc->version, changes->version);
		if( file->dsc->maintainer == NULL )
			fprintf(stderr,
"ERROR: No maintainer specified in '%s'!\n", file->fullfilename);
		else if( changes->maintainer != NULL &&
				strcmp(changes->maintainer, file->dsc->maintainer) != 0 )
			fprintf(stderr,
"Warning: '%s' lists Maintainer '%s' while .changes lists '%s'!\n",
				file->fullfilename,
				file->dsc->maintainer, changes->maintainer);
		if( file->dsc->section != NULL && file->section != NULL &&
				strcmp(file->section, file->dsc->section) != 0 )
			fprintf(stderr,
"Warning: '%s' has Section '%s' while .changes says it is '%s'!\n",
				file->fullfilename,
				file->dsc->section, file->section);
		if( file->dsc->priority != NULL && file->priority != NULL &&
				strcmp(file->priority, file->dsc->priority) != 0 )
			fprintf(stderr,
"Warning: '%s' has Priority '%s' while .changes says it is '%s'!\n",
				file->fullfilename,
				file->dsc->priority, file->priority);
		// Todo: check types of files it contains...
		// check names are sensible
		p = file->basename;
		while( *p != '\0' && *p != '_' )
			p++;
		if( *p == '_' ) {
			l = strlen(p+1);
			assert( l >= 4 ); /* It ends in ".dsc" to come here */
		} else
			l = 0;

		if( file->dsc->name != NULL ) {
			name = file->dsc->name;
			namelen = strlen(name);
		} else {
			// TODO: more believe file name or changes name?
			if( changes->name != NULL ) {
				name = changes->name;
#ifdef STUPIDCC
				namelen = strlen(name);
#endif
			} else {
				if( *p != '_' ) {
					name = NULL;
					namelen = 0;
					fprintf(stderr,
"Warning: '%s' does not contain a '_' separating name and version!\n",
						file->basename);
				}else {
					name = file->basename;
					namelen = p-name;
				}
			}
		}
		if( file->dsc->version != NULL ) {
			version = file->dsc->version;
			versionlen = strlen(version);
		} else {
			// TODO: dito
			if( changes->version != NULL ) {
				version = changes->version;
				versionlen = strlen(version);
			} else {
				if( *p != '_' ) {
					version = NULL;
#ifdef STUPIDCC
					versionlen = 0;
#endif
					if( name != NULL )
						fprintf(stderr,
"ERROR: '%s' does not contain a '_' separating name and version!\n",
							file->basename);
				} else {
					version = p+1;
					versionlen = l-4;
				}
			}
		}
		if( version != NULL ) {
			const char *colon = strchr(version, ':');
			if( colon != NULL ) {
				colon++;
				versionlen -= (colon-version);
				version = colon;
			}
		}
		if( name != NULL && version != NULL ) {
			if( *p != '_'
			|| (size_t)(p-file->basename) != namelen || l-4 != versionlen
			|| strncmp(p+1, version, versionlen) != 0
			|| strncmp(file->basename, name, namelen) != 0 )
				fprintf(stderr,
"ERROR: '%s' is not called '%*s_%*s.dsc' as expected!\n",
					file->basename,
					(unsigned int)namelen, name,
					(unsigned int)versionlen, version);
		}
		has_tar = false;
		has_diff = false;
		has_orig = false;
		for( j = 0 ; j < file->dsc->expected.names.count ; j++ ) {
			const char *basename = file->dsc->expected.names.values[j];
			const struct fileentry *sfile = file->dsc->uplink[j];
			size_t expectedversionlen;

			switch( sfile->type ) {
				case ft_UNKNOWN:
					fprintf(stderr,
"ERROR: '%s' lists a file '%s' with unrecognized suffix!\n",
						file->fullfilename,
						basename);
					break;
				case ft_TAR_GZ:
				case ft_TAR_BZ2:
				case ft_TAR_LZMA:
					if( has_tar || has_orig )
						fprintf(stderr,
"ERROR: '%s' lists multiple .tar files!\n",
						file->fullfilename);
					has_tar = true;
					break;
				case ft_ORIG_TAR_GZ:
				case ft_ORIG_TAR_BZ2:
				case ft_ORIG_TAR_LZMA:
					if( has_tar || has_orig )
						fprintf(stderr,
"ERROR: '%s' lists multiple .tar files!\n",
						file->fullfilename);
					has_orig = true;
					break;
				case ft_DIFF_GZ:
				case ft_DIFF_BZ2:
				case ft_DIFF_LZMA:
					if( has_diff )
						fprintf(stderr,
"ERROR: '%s' lists multiple .diff files!\n",
						file->fullfilename);
					has_diff = true;
					break;
				default:
					assert( sfile->type == ft_UNKNOWN );
			}

			if( name == NULL ) // TODO: try extracting it from this
				continue;
			if( strncmp(sfile->basename, name, namelen) != 0
					|| sfile->basename[namelen] != '_' )
				fprintf(stderr,
"ERROR: '%s' does not begin with '%*s_' as expected!\n",
					sfile->basename,
					(unsigned int)namelen, name);

			if( version == NULL )
				continue;

			if(sfile->type == ft_ORIG_TAR_GZ
					|| sfile->type == ft_ORIG_TAR_BZ2) {
				const char *p, *revision;
				revision = NULL;
				for( p=version; *p != '\0'; p++ ) {
					if( *p == '-' )
						revision = p;
				}
				if( revision == NULL )
					expectedversionlen = versionlen;
				else
					expectedversionlen = revision - version;
			} else
				expectedversionlen = versionlen;

			if( strncmp(sfile->basename+namelen+1,
					version, expectedversionlen) != 0
				|| ( sfile->type != ft_UNKNOWN &&
					strcmp(sfile->basename+namelen+1
						+expectedversionlen,
					typesuffix[sfile->type]) != 0 ))
				fprintf(stderr,
"ERROR: '%s' is not called '%*s_%*s%s' as expected!\n",
					sfile->basename,
					(unsigned int)namelen, name,
					(unsigned int)expectedversionlen,
					version,
					typesuffix[sfile->type]);
		}
		if( !has_tar && !has_orig )
			if( has_diff )
				fprintf(stderr,
"ERROR: '%s' lists only a .diff, but no .orig.tar!\n",
						file->fullfilename);
			else
				fprintf(stderr,
"ERROR: '%s' lists no source files!\n",
						file->fullfilename);
		else if( has_diff && !has_orig )
			fprintf(stderr,
"ERROR: '%s' lists a .diff, but the .tar is not called .orig.tar!\n",
					file->fullfilename);
		else if( !has_diff && has_orig )
			fprintf(stderr,
"ERROR: '%s' lists a .orig.tar, but no .diff!\n",
					file->fullfilename);
	}
	printf("Checking Binary consistency...\n");
	for( j = 0 ; j < changes->binarycount ; j++ ) {
		struct binary *b = &changes->binaries[j];

		if( b->files == NULL && !b->uncheckable ) {
			/* no files - not even conjectured -, headers must be wrong */

			if( b->description != NULL && !b->missedinheader ) {
				fprintf(stderr,
"ERROR: '%s' has binary '%s' in 'Binary:' and 'Description:' header, but no files for it found!\n",
					changesfilename, b->name);
			} else if( b->description != NULL) {
				fprintf(stderr,
"ERROR: '%s' has unexpected description of '%s'\n",
					changesfilename, b->name);
			} else {
				assert( !b->missedinheader );
				fprintf(stderr,
"ERROR: '%s' has unexpected Binary: '%s'\n",
					changesfilename, b->name);
			}
		}
		if( b->files == NULL )
			continue;
		/* files are there, make sure they are listed and
		 * have a description*/

		if( b->description == NULL ) {
			fprintf(stderr,
"ERROR: '%s' has no description for '%s'\n",
				changesfilename, b->name);
		}
		if( b->missedinheader ) {
				fprintf(stderr,
"ERROR: '%s' does not list '%s' in its Binary header!\n",
					changesfilename, b->name);
		}
		// TODO: check if the files have the names they should
		// have an architectures as they are listed...
	}
	for( file = changes->files; file != NULL ; file = file->next ) {
		const struct binary *b;
		const struct binaryfile *deb;

		if( file->type != ft_DEB && file->type != ft_UDEB )
			continue;
		if( file->fullfilename == NULL ) {
			fprintf(stderr,
"ERROR: Could not find '%s'!\n", file->basename);
			continue;
		}
		if( file->deb == NULL ) {
			fprintf(stderr,
"WARNING: Could not read '%s', thus it cannot be checked!\n", file->fullfilename);
			continue;
		}
		deb = file->deb;
		b = deb->binary;

		if( deb->shortdescription == NULL )
			fprintf(stderr,
"Warning: '%s' contains no description!\n",
				file->fullfilename);
		else if( b->description != NULL &&
			 strcmp( b->description, deb->shortdescription) != 0 )
				fprintf(stderr,
"Warning: '%s' says '%s' has description '%s' while '%s' has '%s'!\n",
					changesfilename, b->name,
					b->description,
					file->fullfilename,
					deb->shortdescription);
		if( deb->name == NULL )
			fprintf(stderr,
"ERROR: '%s' does not contain a 'Package:' header!\n", file->fullfilename);
		if( deb->sourcename != NULL ) {
			if( strcmp(changes->name, deb->sourcename) != 0 )
				fprintf(stderr,
"ERROR: '%s' lists Source '%s' while .changes lists '%s'!\n",
					file->fullfilename,
					deb->sourcename, changes->name);
		} else if( deb->name != NULL &&
				strcmp(changes->name, deb->name) != 0 ) {
			fprintf(stderr,
"ERROR: '%s' lists Source '%s' while .changes lists '%s'!\n",
				file->fullfilename,
				deb->name, changes->name);
		}
		if( deb->version == NULL )
			fprintf(stderr,
"ERROR: '%s' does not contain a 'Version:' header!\n", file->fullfilename);
		if( deb->sourceversion != NULL ) {
			if( strcmp(changes->version, deb->sourceversion) != 0 )
				fprintf(stderr,
"ERROR: '%s' lists Source version '%s' while .changes lists '%s'!\n",
					file->fullfilename,
					deb->sourceversion, changes->version);
		} else if( deb->version != NULL &&
				strcmp(changes->version, deb->version) != 0 ) {
			fprintf(stderr,
"ERROR: '%s' lists Source version '%s' while .changes lists '%s'!\n",
				file->fullfilename,
				deb->version, changes->name);
		}

		if( deb->maintainer == NULL )
			fprintf(stderr,
"ERROR: No maintainer specified in '%s'!\n", file->fullfilename);
		else if( changes->maintainer != NULL &&
				strcmp(changes->maintainer, deb->maintainer) != 0 )
			fprintf(stderr,
"Warning: '%s' lists Maintainer '%s' while .changes lists '%s'!\n",
				file->fullfilename,
				deb->maintainer, changes->maintainer);
		if( deb->section == NULL )
			fprintf(stderr,
"ERROR: No section specified in '%s'!\n", file->fullfilename);
		else if( file->section != NULL &&
				strcmp(file->section, deb->section) != 0 )
			fprintf(stderr,
"Warning: '%s' has Section '%s' while .changes says it is '%s'!\n",
				file->fullfilename,
				deb->section, file->section);
		if( deb->priority == NULL )
			fprintf(stderr,
"ERROR: No priority specified in '%s'!\n", file->fullfilename);
		else if( file->priority != NULL &&
				strcmp(file->priority, deb->priority) != 0 )
			fprintf(stderr,
"Warning: '%s' has Priority '%s' while .changes says it is '%s'!\n",
				file->fullfilename,
				deb->priority, file->priority);
		verify_binary_name(file->basename, deb->name, deb->version,
				deb->architecture, file->type);
		if( deb->architecture != NULL
		&& !strlist_in(&changes->architectures,  deb->architecture) ) {
			fprintf(stderr,
"ERROR: '%s' does not list Architecture: '%s' needed for '%s'!\n",
				changesfilename, deb->architecture,
				file->fullfilename);
		}
		// todo: check for md5sums file, verify it...
	}

	printf("Checking checksums...\n");
	r = getchecksums(changes);
	if( RET_WAS_ERROR(r) )
		return r;
	for( file = changes->files; file != NULL ; file = file->next ) {

		if( file->fullfilename == NULL ) {
			fprintf(stderr, "WARNING: Could not check checksums of '%s' as file not found!\n", file->basename);
			if( file->type == ft_DSC ) {
				fprintf(stderr, "WARNING: This file most likely contains additional checksums which could also not be checked because it was not found!\n");
			}
			continue;
		}
		if( file->checksumsfromchanges == NULL )
			/* nothing to check here */
			continue;

		if( file->realchecksums == NULL ) {
			fprintf(stderr, "WARNING: Could not check checksums of '%s'! File vanished while checking or not readable?\n", file->basename);
		} else if( !checksums_check(file->realchecksums, file->checksumsfromchanges, NULL)) {
			fprintf(stderr, "ERROR: checksums of '%s' differ from those listed in .changes:\n",
					file->fullfilename);
			checksums_printdifferences(stderr,
					file->checksumsfromchanges,
					file->realchecksums);
		}

		if( file->type == ft_DSC ) {
			unsigned int i;

			if( file->dsc == NULL ) {
				fprintf(stderr,
"WARNING: Could not read '%s', thus the content cannot be checked\n"
" and may be faulty and other things depending on it may be incorrect!\n", file->basename);
				continue;
			}

			for( i = 0 ; i < file->dsc->expected.names.count ; i++ ) {
				verify_sourcefile_checksums(file->dsc, i,
						file->fullfilename);
			}
		}
		// TODO: check .deb files
	}
	return RET_OK;
}

static bool isarg(int argc, char **argv, const char *name) {
	while( argc > 0 ) {
		if( strcmp(*argv, name) == 0 )
			return true;
		argc--;
		argv++;
	}
	return false;
}

static bool improvedchecksum_supported(const struct changes *c, bool improvedfilehashes[cs_hashCOUNT]) {
	enum checksumtype cs;
	struct fileentry *file;

	for( cs = cs_md5sum ; cs < cs_hashCOUNT ; cs++ ) {
		if( !improvedfilehashes[cs] )
			continue;
		for( file = c->files; file != NULL ; file = file->next ) {
			const char *dummy1, *dummy3;
			size_t dummy2, dummy4;

			if( file->checksumsfromchanges == NULL )
				continue;

			if( !checksums_gethashpart(file->checksumsfromchanges,
						cs,
						&dummy1, &dummy2,
						&dummy3, &dummy4))
				break;
		}
		if( file == NULL )
			return true;
	}
	return false;
}

static bool anyset(bool *list, size_t count) {
	while( count > 0 )
		if( list[--count] )
			return true;
	return false;
}

static retvalue updatechecksums(const char *changesfilename, struct changes *c, int argc, char **argv) {
	retvalue r;
	struct fileentry *file;
	bool improvedfilehashes[cs_hashCOUNT];

	r = getchecksums(c);
	if( RET_WAS_ERROR(r) )
		return r;
	/* first update all .dsc files and perhaps recalculate their checksums */
	for( file = c->files; file != NULL ; file = file->next ) {
		unsigned int i;
		bool improvedhash[cs_hashCOUNT];

		if( file->type != ft_DSC )
			continue;

		if( file->dsc == NULL ) {
			fprintf(stderr,
"WARNING: Could not read '%s', hopeing the content and its checksums are correct!\n",
					file->basename);
			continue;
		}
		memset(improvedhash, 0, sizeof(improvedhash));

		assert( file->fullfilename != NULL );
		for( i = 0 ; i < file->dsc->expected.names.count ; i++ ) {
			const char *basename = file->dsc->expected.names.values[i];
			const struct fileentry *sfile = file->dsc->uplink[i];
			struct checksums **expected_p = &file->dsc->expected.checksums[i];
			const struct checksums * const expected = *expected_p;
			bool doit;
			bool improves;

			assert( expected != NULL );
			assert( basename != NULL );

			doit = isarg(argc,argv,basename);
			if( argc > 0 && !doit )
				continue;

			assert( sfile != NULL );
			if( sfile->checksumsfromchanges == NULL ) {
				if( !doit ) {
					fprintf(stderr,
"Not checking/updating '%s' as not in .changes and not specified on command line.\n",
						basename);
					continue;
				}
				if( sfile->realchecksums == NULL ) {
					fprintf(stderr, "WARNING: Could not check checksums of '%s'!\n", basename);
					continue;
				}
			} else {
				if( sfile->realchecksums == NULL ) {
					fprintf(stderr, "WARNING: Could not check checksums of '%s'!\n", basename);
					continue;
				}
			}

			if( checksums_check(expected, sfile->realchecksums, &improves) ) {
				if( !improves ) {
					/* already correct */
					continue;
				}
				/* future versions might be able to store them in the dsc */
				r = checksums_combine(expected_p,
						sfile->realchecksums,
						improvedhash);
				if( RET_WAS_ERROR(r) )
					return r;
				continue;
			}
			fprintf(stderr,
"Going to update '%s' in '%s'\nfrom '%s'\nto   '%s'.\n",
					basename, file->fullfilename,
					checksums_getmd5sum(expected),
					checksums_getmd5sum(sfile->realchecksums));
			checksums_free(*expected_p);
			*expected_p = checksums_dup(sfile->realchecksums);
			if( *expected_p == NULL )
				return RET_ERROR_OOM;
			file->dsc->modified = true;
		}
		checksumsarray_resetunsupported(&file->dsc->expected,
				improvedhash);
		if( file->dsc->modified | anyset(improvedhash, cs_hashCOUNT) ) {
			r = write_dsc_file(file, DSC_WRITE_FILES);
			if( RET_WAS_ERROR(r) )
				return r;
		}
	}
	memset(improvedfilehashes, 0, sizeof(improvedfilehashes));
	for( file = c->files; file != NULL ; file = file->next ) {
		bool improves;

		if( file->checksumsfromchanges == NULL )
			/* nothing to check here */
			continue;
		if( file->realchecksums == NULL ) {
			fprintf(stderr, "WARNING: Could not check checksums of '%s'! Leaving it as it is.\n", file->basename);
			continue;
		}
		if( checksums_check(file->checksumsfromchanges, file->realchecksums, &improves) ) {
			if( !improves )
				continue;
			/* future versions might store sha sums in .changes: */
			r = checksums_combine(&file->checksumsfromchanges,
					file->realchecksums, improvedfilehashes);
			if( RET_WAS_ERROR(r) )
				return r;
			continue;
		}
		fprintf(stderr,
"Going to update '%s' in '%s'\nfrom '%s'\nto   '%s'.\n",
				file->basename, changesfilename,
				checksums_getmd5sum(file->checksumsfromchanges),
				checksums_getmd5sum(file->realchecksums));
		checksums_free(file->checksumsfromchanges);
		file->checksumsfromchanges = checksums_dup(file->realchecksums);
		if( file->checksumsfromchanges == NULL )
			return RET_ERROR_OOM;
		c->modified = true;
	}
	if( c->modified ) {
		return write_changes_file(changesfilename, c, CHANGES_WRITE_FILES);
	} else if( improvedchecksum_supported(c, improvedfilehashes) ) {
		return write_changes_file(changesfilename, c, CHANGES_WRITE_FILES);
	} else
		return RET_NOTHING;
}

static retvalue includeallsources(const char *changesfilename, struct changes *c, int argc, char **argv) {
	struct fileentry *file;

	for( file = c->files; file != NULL ; file = file->next ) {
		unsigned int i;

		if( file->type != ft_DSC )
			continue;

		if( file->dsc == NULL ) {
			fprintf(stderr,
"WARNING: Could not read '%s', thus cannot determine if it depends on unlisted files!\n",
					file->basename);
			continue;
		}
		assert( file->fullfilename != NULL );
		for( i = 0 ; i < file->dsc->expected.names.count ; i++ ) {
			const char *basename = file->dsc->expected.names.values[i];
			struct fileentry * const sfile = file->dsc->uplink[i];
			struct checksums **expected_p = &file->dsc->expected.checksums[i];
			const struct checksums * const expected = *expected_p;

			assert( expected != NULL );
			assert( basename != NULL );
			assert( sfile != NULL );

			if( sfile->checksumsfromchanges != NULL )
				continue;

			if( argc > 0 && !isarg(argc, argv, basename) )
				continue;

			sfile->checksumsfromchanges = checksums_dup(expected);
			if( sfile->checksumsfromchanges == NULL )
				return RET_ERROR_OOM;
			/* copy section and priority information from the dsc */
			if( sfile->section == NULL && file->section != NULL ) {
				sfile->section = strdup(file->section);
				if( sfile->section == NULL )
					return RET_ERROR_OOM;
			}
			if( sfile->priority == NULL && file->priority != NULL ) {
				sfile->priority = strdup(file->priority);
				if( sfile->priority == NULL )
					return RET_ERROR_OOM;
			}

			fprintf(stderr,
"Going to add '%s' to '%s'.\n",		 basename, changesfilename);
			c->modified = true;
		}
	}
	if( c->modified ) {
		return write_changes_file(changesfilename, c, CHANGES_WRITE_FILES);
	} else
		return RET_NOTHING;
}

static retvalue adddsc(struct changes *c, const char *dscfilename, const struct strlist *searchpath) {
	retvalue r;
	struct fileentry *f;
	struct dscfile *dsc;
	char *fullfilename, *basename;
	char *origdirectory;
	size_t i;

	r = findfile(dscfilename, c, searchpath, ".", &fullfilename);
	if( RET_WAS_ERROR(r) )
		return r;
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Cannot find '%s'!\n", dscfilename);
		return RET_ERROR_MISSING;
	}
	r = read_dscfile(fullfilename, &dsc);
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Error reading '%s'!\n", fullfilename);
		r = RET_ERROR;
	}
	if( RET_WAS_ERROR(r) ) {
		free(fullfilename);
		return r;
	}
	if( dsc->name == NULL || dsc->version == NULL ) {
		if( dsc->name == NULL )
			fprintf(stderr, "Could not extract name of '%s'!\n",
					fullfilename);
		else
			fprintf(stderr, "Could not extract version of '%s'!\n",
					fullfilename);
		dscfile_free(dsc);
		free(fullfilename);
		return RET_ERROR;
	}
	if( c->name != NULL ) {
		if( strcmp(c->name, dsc->name) != 0 ) {
			fprintf(stderr,
"ERROR: '%s' lists source '%s' while '%s' already is '%s'!\n",
					fullfilename, dsc->name,
					c->filename, c->name);
			dscfile_free(dsc);
			free(fullfilename);
			return RET_ERROR;
		}
	} else {
		c->name = strdup(dsc->name);
		if( c->name == NULL ) {
			dscfile_free(dsc);
			free(fullfilename);
			return RET_ERROR_OOM;
		}
	}
	if( c->version != NULL ) {
		if( strcmp(c->version, dsc->version) != 0 )
			fprintf(stderr,
"WARNING: '%s' lists version '%s' while '%s' already lists '%s'!\n",
					fullfilename, dsc->version,
					c->filename, c->version);
	} else {
		c->version = strdup(dsc->version);
		if( c->version == NULL ) {
			dscfile_free(dsc);
			free(fullfilename);
			return RET_ERROR_OOM;
		}
	}
	// TODO: make sure if the .changes name/version are modified they will
	// also be written...
	basename = calc_source_basename(dsc->name, dsc->version);
	if( basename == NULL ) {
		dscfile_free(dsc);
		free(fullfilename);
		return RET_ERROR_OOM;
	}

	r = dirs_getdirectory(fullfilename, &origdirectory);
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(dsc);
		free(origdirectory);
		free(fullfilename);
		return RET_ERROR_OOM;
	}

	// TODO: add rename/copy option to be activated when old and new
	// basename differ

	r = add_file(c, basename, fullfilename, ft_DSC, &f);
	if( RET_WAS_ERROR(r) ) {
		dscfile_free(dsc);
		free(origdirectory);
		free(fullfilename);
		free(basename);
		return r;
	}
	if( r == RET_NOTHING ) {
		fprintf(stderr, "ERROR: '%s' already contains a file of the same name!\n", c->filename);
		dscfile_free(dsc);
		free(origdirectory);
		free(fullfilename);
		free(basename);
		// TODO: check instead if it is already the same...
		return RET_ERROR;
	}
	/* f owns dsc, fullfilename and basename now */
	f->dsc = dsc;

	/* now include the files needed by this */
	for( i =  0 ; i < dsc->expected.names.count ; i++ ) {
		struct fileentry *file;
		const char *basefilename = dsc->expected.names.values[i];
		const struct checksums *checksums = dsc->expected.checksums[i];

		file = add_fileentry(c, basefilename,
				strlen(basefilename),
				true, NULL);
		if( file == NULL ) {
			free(origdirectory);
			return RET_ERROR_OOM;
		}
		dsc->uplink[i] = file;
		/* make them appear in the .changes file if not there: */
		// TODO: add missing checksums here from file
		if( file->checksumsfromchanges == NULL ) {
			file->checksumsfromchanges = checksums_dup(checksums);
			if( file->checksumsfromchanges == NULL ) {
				free(origdirectory);
				return RET_ERROR_OOM;
			}
		} // TODO: otherwise warn if not the same
	}

	c->modified = true;
	r = checksums_read(f->fullfilename, &f->realchecksums);
	if( RET_WAS_ERROR(r) ) {
		free(origdirectory);
		return r;
	}
	f->checksumsfromchanges = checksums_dup(f->realchecksums);
	if( f->checksumsfromchanges == NULL ) {
		free(origdirectory);
		return RET_ERROR_OOM;;
	}
	/* for a "extended" dsc with section or priority */
	if( dsc->section != NULL ) {
		free(f->section);
		f->section = strdup(dsc->section);
		if( FAILEDTOALLOC(f->section) ) {
			free(origdirectory);
			return RET_ERROR_OOM;
		}
	}
	if( dsc->priority != NULL ) {
		free(f->priority);
		f->priority = strdup(dsc->priority);
		if( FAILEDTOALLOC(f->priority) ) {
			free(origdirectory);
			return RET_ERROR_OOM;
		}
	}
	if( f->section == NULL || f->priority == NULL ) {
		struct sourceextraction *extraction;
		int j;

		extraction = sourceextraction_init(
				(f->section == NULL)?&f->section:NULL,
				(f->priority == NULL)?&f->priority:NULL);
		if( FAILEDTOALLOC(extraction) ) {
			free(origdirectory);
			return RET_ERROR_OOM;
		}
		for( j = 0 ; j < dsc->expected.names.count ; j++ ) {
			sourceextraction_setpart(extraction, j,
					dsc->expected.names.values[j]);
		}
		while( sourceextraction_needs(extraction, &j) ) {
			if( dsc->uplink[j]->fullfilename == NULL ) {
				/* look for file */
				r = findfile(dsc->expected.names.values[j], c,
					searchpath, origdirectory,
					&dsc->uplink[j]->fullfilename);
				if( RET_WAS_ERROR(r) ) {
					sourceextraction_abort(extraction);
					free(origdirectory);
					return r;
				}
				if( r == RET_NOTHING ||
				    dsc->uplink[j]->fullfilename == NULL )
					break;
			}
			r = sourceextraction_analyse(extraction,
					dsc->uplink[j]->fullfilename);
			if( RET_WAS_ERROR(r) ) {
				sourceextraction_abort(extraction);
				free(origdirectory);
				return r;
			}
		}
		r = sourceextraction_finish(extraction);
		if( RET_WAS_ERROR(r) ) {
			free(origdirectory);
			return r;
		}
	}
	free(origdirectory);
	/* update information in the main .changes file if not there already */
	if( c->maintainer == NULL && dsc->maintainer != NULL ) {
		c->maintainer = strdup(dsc->maintainer);
		if( c->maintainer == NULL )
			return RET_ERROR_OOM;
	}
	if( !strlist_in(&c->architectures, "source") ) {
		r = strlist_add_dup(&c->architectures, "source");
		if( RET_WAS_ERROR(r) )
			return r;
	}
	return RET_OK;
}

static retvalue adddscs(const char *changesfilename, struct changes *c, int argc, char **argv, const struct strlist *searchpath) {
	if( argc <= 0 ) {
		fprintf(stderr, "Filenames of .dsc files to include expected!\n");
		return RET_ERROR;
	}
	while( argc > 0 ) {
		retvalue r = adddsc(c, argv[0], searchpath);
		if( RET_WAS_ERROR(r) )
			return r;
		argc--; argv++;
	}
	if( c->modified ) {
		return write_changes_file(changesfilename, c,
				CHANGES_WRITE_ALL);
	} else
		return RET_NOTHING;
}

static retvalue adddeb(struct changes *c, const char *debfilename, const struct strlist *searchpath) {
	retvalue r;
	struct fileentry *f;
	struct binaryfile *deb;
	const char *packagetype;
	enum filetype type;
	char *fullfilename, *basename;

	r = findfile(debfilename, c, searchpath, ".", &fullfilename);
	if( RET_WAS_ERROR(r) )
		return r;
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Cannot find '%s'!\n", debfilename);
		return RET_ERROR_MISSING;
	}
	r = read_binaryfile(fullfilename, &deb);
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Error reading '%s'!\n", fullfilename);
		r = RET_ERROR;
	}
	if( RET_WAS_ERROR(r) ) {
		free(fullfilename);
		return r;
	}
	// TODO: check if there are other things but the name to distinguish them
	if( strlen(fullfilename) > 5 &&
			strcmp(fullfilename+strlen(fullfilename)-5,".udeb") == 0 ) {
		packagetype = "udeb";
		type = ft_UDEB;
	} else {
		packagetype = "deb";
		type = ft_DEB;
	}
	if( deb->name == NULL || deb->version == NULL || deb->architecture == NULL ) {
		if( deb->name == NULL )
			fprintf(stderr, "Could not extract packagename of '%s'!\n",
					fullfilename);
		else if( deb->version == NULL )
			fprintf(stderr, "Could not extract version of '%s'!\n",
					fullfilename);
		else
			fprintf(stderr, "Could not extract architecture of '%s'!\n",
					fullfilename);
		binaryfile_free(deb);
		free(fullfilename);
		return RET_ERROR;
	}
	if( c->name != NULL ) {
		const char *sourcename;
		if( deb->sourcename != NULL )
			sourcename = deb->sourcename;
		else
			sourcename = deb->name;
		if( strcmp(c->name,sourcename) != 0 ) {
			fprintf(stderr,
"ERROR: '%s' lists source '%s' while '%s' already is '%s'!\n",
					fullfilename, sourcename,
					c->filename, c->name);
			binaryfile_free(deb);
			free(fullfilename);
			return RET_ERROR;
		}
	} else {
		if( deb->sourcename != NULL )
			c->name = strdup(deb->sourcename);
		else
			c->name = strdup(deb->name);
		if( c->name == NULL ) {
			binaryfile_free(deb);
			free(fullfilename);
			return RET_ERROR_OOM;
		}
	}
	if( c->version != NULL ) {
		const char *sourceversion;
		if( deb->sourceversion != NULL )
			sourceversion = deb->sourceversion;
		else
			sourceversion = deb->version;
		if( strcmp(c->version,sourceversion) != 0 )
			fprintf(stderr,
"WARNING: '%s' lists source version '%s' while '%s' already lists '%s'!\n",
					fullfilename, sourceversion,
					c->filename, c->version);
	} else {
		if( deb->sourceversion != NULL )
			c->version = strdup(deb->sourceversion);
		else
			c->version = strdup(deb->version);
		if( c->version == NULL ) {
			binaryfile_free(deb);
			free(fullfilename);
			return RET_ERROR_OOM;
		}
	}
	// TODO: make sure if the .changes name/version are modified they will
	// also be written...
	basename = calc_binary_basename(deb->name, deb->version,
	                                deb->architecture, packagetype);
	if( basename == NULL ) {
		binaryfile_free(deb);
		free(fullfilename);
		return RET_ERROR_OOM;
	}

	// TODO: add rename/copy option to be activated when old and new
	// basename differ

	r = add_file(c, basename, fullfilename, type, &f);
	if( RET_WAS_ERROR(r) ) {
		binaryfile_free(deb);
		free(fullfilename);
		free(basename);
		return r;
	}
	if( r == RET_NOTHING ) {
		fprintf(stderr, "ERROR: '%s' already contains a file of the same name!\n", c->filename);
		binaryfile_free(deb);
		free(fullfilename);
		free(basename);
		// TODO: check instead if it is already the same...
		return RET_ERROR;
	}
	/* f owns deb, fullfilename and basename now */
	f->deb = deb;
	deb->binary = get_binary(c, deb->name, strlen(deb->name));
	if( deb->binary == NULL ) {
		return RET_ERROR_OOM;
	}
	deb->next = deb->binary->files;
	deb->binary->files = deb;
	deb->binary->missedinheader = false;
	c->modified = true;
	r = checksums_read(f->fullfilename, &f->realchecksums);
	if( RET_WAS_ERROR(r) ) {
		return r;
	}
	f->checksumsfromchanges = checksums_dup(f->realchecksums);
	if( f->checksumsfromchanges == NULL ) {
		return RET_ERROR_OOM;;
	}
	if( deb->shortdescription != NULL ) {
		if( deb->binary->description == NULL ) {
			deb->binary->description = strdup(deb->shortdescription);
			deb->binary->missedinheader = false;
		} else if( strcmp(deb->binary->description,
		                  deb->shortdescription) != 0 ) {
			fprintf(stderr,
"WARNING: '%s' already lists a different description for '%s' than contained in '%s'!\n",
					c->filename, deb->name, fullfilename);
		}
	}
	if( deb->section != NULL ) {
		free(f->section);
		f->section = strdup(deb->section);
	}
	if( deb->priority != NULL ) {
		free(f->priority);
		f->priority = strdup(deb->priority);
	}
	if( c->maintainer == NULL && deb->maintainer != NULL ) {
		c->maintainer = strdup(deb->maintainer);
	}
	if( deb->architecture != NULL &&
			!strlist_in(&c->architectures, deb->architecture) ) {
		strlist_add_dup(&c->architectures, deb->architecture);
	}
	return RET_OK;
}

static retvalue adddebs(const char *changesfilename, struct changes *c, int argc, char **argv, const struct strlist *searchpath) {
	if( argc <= 0 ) {
		fprintf(stderr, "Filenames of .deb files to include expected!\n");
		return RET_ERROR;
	}
	while( argc > 0 ) {
		retvalue r = adddeb(c, argv[0], searchpath);
		if( RET_WAS_ERROR(r) )
			return r;
		argc--; argv++;
	}
	if( c->modified ) {
		return write_changes_file(changesfilename, c,
				CHANGES_WRITE_ALL);
	} else
		return RET_NOTHING;
}

static retvalue addrawfile(struct changes *c, const char *filename, const struct strlist *searchpath) {
	retvalue r;
	struct fileentry *f;
	char *fullfilename, *basefilename;
	struct checksums *checksums;

	r = findfile(filename, c, searchpath, ".", &fullfilename);
	if( RET_WAS_ERROR(r) )
		return r;
	if( r == RET_NOTHING ) {
		fprintf(stderr, "Cannot find '%s'!\n", filename);
		return RET_ERROR_MISSING;
	}
	basefilename = strdup(dirs_basename(filename));
	if( basefilename == NULL ) {
		free(fullfilename);
		return RET_ERROR_OOM;
	}
	r = checksums_read(fullfilename, &checksums);
	if( RET_WAS_ERROR(r) ) {
		free(fullfilename);
		free(basefilename);
		return r;
	}
	r = add_file(c, basefilename, fullfilename, ft_UNKNOWN, &f);
	if( RET_WAS_ERROR(r) ) {
		free(fullfilename);
		free(basefilename);
		checksums_free(checksums);
		return r;
	}
	if( r == RET_NOTHING ) {

		assert( f != NULL );

		if( f->checksumsfromchanges != NULL ) {
			/* already listed in .changes */

			if( !checksums_check(f->checksumsfromchanges, checksums, NULL) ) {
				fprintf(stderr, "ERROR: '%s' already contains a file with name '%s' but different checksums!\n", c->filename, basefilename);
				free(fullfilename);
				free(basefilename);
				checksums_free(checksums);
				return RET_ERROR;
			}
			printf("'%s' already lists '%s' with same checksums. Doing nothing.\n", c->filename, basefilename);
			free(fullfilename);
			free(basefilename);
			checksums_free(checksums);
			return RET_NOTHING;
		} else {
			/* file already expected by some other part (e.g. a .dsc) */

			// TODO: find out whom this files belong to and warn if different
			free(fullfilename);
			free(basefilename);
		}
	} else {
		// fullfilename and basefilename now belong to *f
		basefilename = NULL;
		fullfilename = NULL;
	}

	c->modified = true;
	assert( f->checksumsfromchanges == NULL );
	f->checksumsfromchanges = checksums;
	checksums = NULL;
	if( f->realchecksums == NULL )
		f->realchecksums = checksums_dup(f->checksumsfromchanges);
	if( f->realchecksums == NULL )
		return RET_ERROR_OOM;;
	return RET_OK;
}

static retvalue addrawfiles(const char *changesfilename, struct changes *c, int argc, char **argv, const struct strlist *searchpath) {
	if( argc <= 0 ) {
		fprintf(stderr, "Filenames of files to add (without further parsing) expected!\n");
		return RET_ERROR;
	}
	while( argc > 0 ) {
		retvalue r = addrawfile(c, argv[0], searchpath);
		if( RET_WAS_ERROR(r) )
			return r;
		argc--; argv++;
	}
	if( c->modified ) {
		return write_changes_file(changesfilename, c,
				CHANGES_WRITE_FILES);
	} else
		return RET_NOTHING;
}

static retvalue addfiles(const char *changesfilename, struct changes *c, int argc, char **argv, const struct strlist *searchpath) {
	if( argc <= 0 ) {
		fprintf(stderr, "Filenames of files to add expected!\n");
		return RET_ERROR;
	}
	while( argc > 0 ) {
		retvalue r;
		const char *filename = argv[0];
		size_t l = strlen(filename);

		if( (l > 4 && strcmp(filename+l-4, ".deb") == 0) ||
		    (l > 5 && strcmp(filename+l-5, ".udeb") == 0) )
			r = adddeb(c, filename, searchpath);
		else if( (l > 4 && strcmp(filename+l-4, ".dsc") == 0) )
			r = adddsc(c, filename, searchpath);
		else
			r = addrawfile(c, argv[0], searchpath);
		if( RET_WAS_ERROR(r) )
			return r;
		argc--; argv++;
	}
	if( c->modified ) {
		return write_changes_file(changesfilename, c,
				CHANGES_WRITE_ALL);
	} else
		return RET_NOTHING;
}

static retvalue setdistribution(const char *changesfilename, struct changes *c, int argc, char **argv) {
	retvalue r;
	struct strlist distributions;
	int i;

	if( argc <= 0 ) {
		fprintf(stderr, "expected Distribution name to set!\n");
		return RET_ERROR;
	}
	r = strlist_init_n(argc, &distributions);
	if( RET_WAS_ERROR(r) )
		return r;
	for( i = 0 ; i < argc ; i++ ) {
		r = strlist_add_dup(&distributions, argv[i]);
		if( RET_WAS_ERROR(r) ) {
			strlist_done(&distributions);
			return r;
		}
	}
	strlist_done(&c->distributions);
	strlist_move(&c->distributions, &distributions);
	return write_changes_file(changesfilename, c,
			CHANGES_WRITE_DISTRIBUTIONS);
}

static int execute_command(int argc, char **argv, const char *changesfilename, const struct strlist *searchpath, bool file_exists, bool create_file, struct changes *changesdata) {
	const char *command = argv[0];
	retvalue r;

	assert( argc > 0 );

	if( strcasecmp(command, "verify") == 0 ) {
		if( argc > 1 ) {
			fprintf(stderr, "Too many arguments!\n");
			r = RET_ERROR;
		} else if( file_exists )
			r = verify(changesfilename, changesdata);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else if( strcasecmp(command, "updatechecksums") == 0 ) {
		if( file_exists )
			r = updatechecksums(changesfilename, changesdata, argc-1, argv+1);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else if( strcasecmp(command, "includeallsources") == 0 ) {
		if( file_exists )
			r = includeallsources(changesfilename, changesdata, argc-1, argv+1);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else if( strcasecmp(command, "addrawfile") == 0 ) {
		if( file_exists || create_file )
			r = addrawfiles(changesfilename, changesdata,
					argc-1, argv+1, searchpath);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else if( strcasecmp(command, "adddsc") == 0 ) {
		if( file_exists || create_file )
			r = adddscs(changesfilename, changesdata,
					argc-1, argv+1, searchpath);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else if( strcasecmp(command, "adddeb") == 0 ) {
		if( file_exists || create_file )
			r = adddebs(changesfilename, changesdata,
					argc-1, argv+1, searchpath);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else if( strcasecmp(command, "add") == 0 ) {
		if( file_exists || create_file )
			r = addfiles(changesfilename, changesdata,
					argc-1, argv+1, searchpath);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else if( strcasecmp(command, "setdistribution") == 0 ) {
		if( file_exists )
			r = setdistribution(changesfilename, changesdata,
					argc-1, argv+1);
		else {
			fprintf(stderr, "No such file '%s'!\n",
					changesfilename);
			r = RET_ERROR;
		}
	} else {
		fprintf(stderr, "Unknown command '%s'\n", command);
		r = RET_ERROR;
	}
	return r;
}

static retvalue splitpath(struct strlist *list, const char *path) {
	retvalue r;
	const char *next;

	while( (next = index(path, ':')) != NULL ) {
		if( next > path ) {
			char *dir = strndup(path, next-path);
			if( dir == NULL ) {
				return RET_ERROR_OOM;
			}
			r = strlist_add(list, dir);
			if( RET_WAS_ERROR(r) )
				return r;
		}
		path = next+1;
	}
	return strlist_add_dup(list, path);
}

int main(int argc,char *argv[]) {
	static const struct option longopts[] = {
		{"help", no_argument, NULL, 'h'},
		{"create", no_argument, NULL, 'C'},
		{"searchpath", required_argument, NULL, 's'},
		{NULL, 0, NULL, 0},
	};
	int c;
	const char *changesfilename;
	bool file_exists;
	bool create_file = false;
	struct strlist validkeys,keys;
	struct strlist searchpath;
	struct changes *changesdata;
	retvalue r;

	strlist_init(&searchpath);

	while( (c = getopt_long(argc,argv,"+hi:s:",longopts,NULL)) != -1 ) {
		switch( c ) {
			case 'h':
				about(true);
			case 'C':
				create_file = true;
				break;
			case 's':
				r = splitpath(&searchpath, optarg);
				if( RET_WAS_ERROR(r) ) {
					if( r == RET_ERROR_OOM )
						fprintf(stderr, "Out of memory!\n");
					exit(EXIT_FAILURE);
				}
				break;
		}
	}
	if( argc - optind < 2 ) {
		about(false);
	}
	signature_init(false);

	changesfilename = argv[optind];
	if( strcmp(changesfilename,"-") != 0 && !endswith(changesfilename,".changes") ) {
		fprintf(stderr, "first argument not ending with '.changes'\n");
		exit(EXIT_FAILURE);
	}
	file_exists = isregularfile(changesfilename);
	if( file_exists ) {
		char *changes;

		r = signature_readsignedchunk(changesfilename, changesfilename,
				&changes, &validkeys, &keys, NULL);
		if( !RET_IS_OK(r) ) {
			signatures_done();
			if( r == RET_ERROR_OOM )
				fprintf(stderr, "Out of memory!\n");
			exit(EXIT_FAILURE);
		}
		r = parse_changes(changesfilename, changes, &changesdata, &searchpath);
		if( RET_IS_OK(r) )
			changesdata->control = changes;
		else {
			free(changes);
			changesdata = NULL;
		}
	} else {
		strlist_init(&keys);
		strlist_init(&validkeys);
		changesdata = calloc(1,sizeof(struct changes));
		if( changesdata != NULL )
			changesdata->filename = strdup(changesfilename);
		if( changesdata == NULL || changesdata->filename == NULL )
			r = RET_ERROR_OOM;
		else {
			r = dirs_getdirectory(changesfilename,
					&changesdata->basedir);
		}
	}

	if( !RET_WAS_ERROR(r) ) {
		argc -= (optind+1);
		argv += (optind+1);
		r = execute_command(argc, argv, changesfilename, &searchpath,
		                    file_exists, create_file, changesdata);
	}
	changes_free(changesdata);
	strlist_done(&keys);

	signatures_done();
	if( RET_IS_OK(r) )
		exit(EXIT_SUCCESS);
	if( r == RET_ERROR_OOM )
		fprintf(stderr, "Out of memory!\n");
	exit(EXIT_FAILURE);
}
